<?php
/**********************************************************************************
WIKINDX: Bibliographic Management system.
Copyright (C)

This program is free software; you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program;
if not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

The WIKINDX Team 2007
sirfragalot@users.sourceforge.net
**********************************************************************************/
/*****
* ATTACHMENTS class
*
* $Header: /cvsroot/wikindx/wikindx3/core/attachments/ATTACHMENTS.php,v 1.22 2007/07/02 18:12:55 sirfragalot Exp $
*****/
class ATTACHMENTS
{
// Constructor
	function ATTACHMENTS($db, $vars)
	{
		$this->db = $db;
		$this->vars = $vars;
		include_once("core/html/MISC.php");
		include_once("core/html/HTML.php");
		$this->html = new HTML();
		include_once("core/icons/LOADICONS.php");
		$this->icons = new LOADICONS();
		include_once("core/session/SESSION.php");
		$this->session = new SESSION();
		include_once("core/messages/MESSAGES.php");
		$this->messages = new MESSAGES();
		include_once("core/messages/SUCCESS.php");
		$this->success = new SUCCESS();
		include_once("core/messages/ERRORS.php");
		$this->errors = new ERRORS();
		include_once("core/template/TEMPLATE.php");
		$this->template = new TEMPLATE('content');
		include_once("core/html/CLOSE.php");
		include("config.php");
		if(!isset($WIKINDX_ATTACHMENTS_DIR))
			$this->dirName = "attachments";
		else
			$this->dirName = $WIKINDX_ATTACHMENTS_DIR;
	}
// check we are allowed to add/delete attachments
	function gateKeep($function)
	{
		if(!array_key_exists('resourceId', $this->vars))
			$this->badInput($this->errors->text("inputError", "invalid"));
		if($this->session->getVar("setup_write"))
		{
// Check file does not exceed upload_max_filesize in php.ini -- $_FILE['file']['error'] will be 1.
			if($maxSize = $this->session->getVar('attachMaxSize'))
			{
				if(!empty($_FILES) && ($_FILES['file']['error'] == 4) && !$_FILES['file']['size'])
					return $this->addDisplay($this->errors->text("inputError", "missing"));
				if(!empty($_FILES) && $_FILES['file']['error'])
					return $this->addDisplay($this->errors->text("file", "fileSize", $maxSize));
			}
			return $this->$function();
		}
// Else require authentication
		include_once("core/authorize/AUTHORIZE.php");
		$authorize = new AUTHORIZE($this->db);
		return $authorize->initLogon();
	}
// Form to add an attachment
	function addDisplay($error = FALSE)
	{
		$this->session->delVar('attachLock');
		$this->template->setVar('heading', $this->messages->text("heading", "attach", 
			" (" . $this->messages->text("submit", "Add") . ")"));
		$pString = '';
// Check file does not exceed max file size -- $_FILE will be empty.  If file input exceeds php.ini variables, PHP automatically sends script back here with empty $_FILES
// if file size exceeds post_max_size in php.ini
		if($error)
			$pString .= $error;
		else if(($maxSize = $this->session->getVar('attachMaxSize')) && empty($_FILES))
			$pString .= $this->errors->text("file", "fileSize", $maxSize);
		$pString .= $this->fileAttachForm();
		$this->template->setVar('body', $pString);
		return $this->template->process();
	}
// form input for adding an attachment
	function fileAttachForm()
	{
		$postMax = $this->return_bytes(ini_get('post_max_size'));
		$uploadMax = $this->return_bytes(ini_get('upload_max_filesize'));
		if($postMax < $uploadMax)
			$maxSize = $postMax;
		else
			$maxSize = $uploadMax;
		$this->session->setVar('attachMaxSize', $maxSize);
		$this->template->setVar('formStart', FORM::formMultiHeader("fileAdd"));
		$pString = FORM::hidden("resourceId", $this->vars['resourceId']);
		$pString .= FORM::hidden("MAX_FILE_SIZE", $maxSize);
		$pString .= TABLE::tableStart(FALSE, 0, 0, 0, "left");
		$pString .= TABLE::trStart();
		$pString .= TABLE::td(FORM::fileUpload($this->messages->text("resources", "fileAttach"), 
			"file", 20), FALSE, "left", "bottom");
		$pString .= TABLE::td($this->messages->text("resources", "fileName") . "&nbsp;" . 
			FORM::textInput(FALSE, 
			"fileName"), FALSE, "left", "bottom");
		$pString .= TABLE::trEnd() . TABLE::trStart();
		$pString .= TABLE::td(MISC::br() . "&nbsp;" . MISC::br() . 
			MISC::p(FORM::formSubmit("Proceed"), FALSE, "right"), FALSE, "left", "top", 5);
		$pString .= TABLE::trEnd();
		$pString .= TABLE::tableEnd();
		return $pString;
	}
// Convert some ini values to numerical values
	function return_bytes($val)
	{
		$val = trim($val);
		$last = strtolower($val{strlen($val)-1});
		switch($last) 
		{
// The 'G' modifier is available since PHP 5.1.0
			case 'g':
			   $val *= 1024;
			case 'm':
			   $val *= 1024;
			case 'k':
			   $val *= 1024;
		}
		return $val;
	}
// Store file input
	function fileAdd()
	{
		include_once("core/resource/RESOURCEVIEW.php");
		$resource = new RESOURCEVIEW($this->db, $this->vars);
		if($this->session->getVar('attachLock'))
			new CLOSE($this->db, $resource->view($this->errors->text("done", "attachAdd"), 'lastSolo'));
		if(isset($_FILES) && array_key_exists('file', $_FILES) && 
			$_FILES['file']['tmp_name'])
		{
			if(trim($this->vars['fileName']))
				$fileName = trim($this->vars['fileName']);
			else if(!get_magic_quotes_gpc())
				$fileName = addslashes($_FILES['file']['name']);
			else
				$fileName = $_FILES['file']['name']; // already slashed
			if(($fileName == '.') || ($fileName == '..'))
				$this->badInput($this->errors->text("file", "upload"));
			$hash = sha1_file($_FILES['file']['tmp_name']);
			$recordSet = $this->db->select(array("WKX_attachments"), array("hashFilename"), 
				" WHERE " . $this->db->formatField("hashFileName") . "=" . $this->db->tidyInput($hash));
			if(!$this->db->numRows($recordSet)) // Doesn't exist, so upload it
			{
				if(!move_uploaded_file($_FILES['file']['tmp_name'], $this->dirName . '/' . $hash))
					$this->badInput($this->errors->text("file", "upload"));
			}
// If $hash already exists for this resourceId, don't write another row
			if($this->db->numRows($recordSet))
			{
				$recordSet2 = $this->db->select(array("WKX_attachments"), array("hashFilename"), 
					" WHERE " . $this->db->formatField("hashFileName") . "=" . $this->db->tidyInput($hash) . 
					" AND " . $this->db->formatField("resourceId") . "=" . 
					$this->db->tidyInput($this->vars['resourceId']));
				if($this->db->numRows($recordSet2)) // Exists, so don't write
					new CLOSE($this->db, $resource->view($this->errors->text("done", "attachAdd"), 'lastSolo'));
			}
			$fields[] = 'hashFilename';
			$values[] = $hash;
			$fields[] = 'resourceId';
			$values[] = $this->vars['resourceId'];
			$fields[] = 'filename';
			$values[] = $fileName;
			$fields[] = 'fileType';
			$values[] = $_FILES['file']['type'];
			$fields[] = 'fileSize';
			$values[] = $_FILES['file']['size'];
			$this->db->insert('WKX_attachments', $fields, $values);
		}
// Lock re-inserting
		$this->session->setVar('attachLock', TRUE);
// send back to view this resource with success message
		new CLOSE($this->db, $resource->view($this->success->text("attach", " " . 
			$this->messages->text("misc", 'added') . " "), 'lastSolo'));
	}
// Show any attached files when viewing a single resource
	function attachedFiles($id)
	{
// When displaying a single resource, this value is used to display no. downloads of attached files or not
		$this->hasAttachments = FALSE;
// Are only logged on users allowed to view this file and is this user logged on?
		if($this->session->getVar("setup_fileViewLoggedOnOnly") && !$this->session->getVar("setup_userId"))
			return FALSE;
		$files = $this->listFiles($id);
		$this->icons->getIconInfo("file");
		$linkClass = $this->icons->fileExists ? "imgLink" : "link";
		if($this->session->getVar('setup_write'))
		{
			$this->icons->getIconInfo("add");
			$addLink = $this->icons->addExists ? "imgLink" : "link";
			$this->icons->getIconInfo("delete");
			$deleteLink = $this->icons->deleteExists ? "imgLink" : "link";
		}
		else
			$addLink = $addNew = $deleteLink = FALSE;
		$write = FALSE;
		if($this->session->getVar("setup_fileAttach"))
			$write = TRUE;
		if(!$write && empty($files))
			return FALSE;
		if(!$this->session->getVar('setup_write') && empty($files))
			return FALSE;
		if($addLink)
			$addNew = $write ? " " . MISC::a($addLink, $this->icons->add, 
				"index.php?action=fileAddDisplay" . htmlentities("&resourceId=" . $id)) : FALSE;
		$pString = TABLE::tableStart(FALSE, 0, 0, 0, "left");
		$pString .= TABLE::trStart();
// If no files, display only icon to add new
		if(empty($files))
			$pString .= TABLE::td(MISC::b($this->messages->text("viewResource", "fileAttached")) . 
				"&nbsp;" . $addNew, "alternate1");
		else
		{
			$numFiles = sizeof($files);
			foreach($files as $hash => $fileName)
			{
				if($numFiles == 1)
					$label = $this->icons->fileExists ? $this->icons->file : $fileName;
				else
				{
					$linkClass = "link";
					$this->icons->file = $fileName;
					$label = stripslashes($fileName);
				}
				$fileName = stripslashes($fileName);
				$fileLinks[] = MISC::a($linkClass, $label, 
					"index.php?action=fileDownload" . 
					htmlentities("&resourceId=$id&hash=$hash&filename=$fileName"), "_new");
			}
			$links = join(", ", $fileLinks);
			if(!empty($files) && $write && $deleteLink)
				$delete = "&nbsp;" . MISC::a($deleteLink, $this->icons->delete, 
				"index.php?action=fileDeleteDisplay" . htmlentities("&resourceId=" . $id));
			else
				$delete = FALSE;
			$pString .= TABLE::td(MISC::b($this->messages->text("viewResource", "fileAttached")) . "&nbsp;&nbsp;" .
				$links . "&nbsp;" . $addNew . $delete, "alternate1");
			$this->hasAttachments = TRUE; // display no. downloads in single resource view
		}
		$pString .= TABLE::trEnd();
		$pString .= TABLE::tableEnd();
		$pString .= MISC::br() . "&nbsp;" . MISC::br();
		return $pString;
	}
// Show list of attachments as downloadable links.
// If $zip, return array of hash filenames ready for tarring and zipping.
	function attachedFileNames($idArray, $zip = FALSE)
	{
// Are only logged on users allowed to view this file and is this user logged on?
		if($this->session->getVar("setup_fileViewLoggedOnOnly") && !$this->session->getVar("setup_userId"))
			return FALSE;
		$linkClass = "link";
		$zipFiles = $allFiles = array();
		foreach($idArray as $id)
		{
			$files = $this->listFiles($id);
			foreach($files as $hash => $fileName)
			{
				if($zip && !empty($files))
				{
					$zipFiles[$hash] = $fileName;
					continue;
				}
				$label = stripslashes($fileName);
				$fileName = stripslashes($fileName);
				$allFiles[strtolower($fileName)] = MISC::a($linkClass, $label, 
					"index.php?action=fileDownload" . 
					htmlentities("&resourceId=$id&hash=$hash&filename=$fileName"), "_new");
			}
		}
		if(!empty($zipFiles))
			return $zipFiles;
		if(empty($allFiles))
			return FALSE;
		ksort($allFiles);
		return $allFiles;
	}
// List all files for this resource
	function listFiles($resourceId)
	{
		$array = array();
		$recordSet = $this->db->select(array("WKX_attachments"), array("hashFilename", "filename"), 
			" WHERE " . $this->db->formatField("resourceId") . "=" . $this->db->tidyInput($resourceId) . 
			" ORDER BY " . $this->db->formatfield("filename"));
		while($row = $this->db->loopRecordSet($recordSet))
			$array[$row['hashFilename']] = stripslashes($row['filename']);
		return $array;
	}
// Delete attachments display.
	function deleteDisplay($error = FALSE)
	{
		$this->session->delVar('attachLock');
		$this->template->setVar("heading", $this->messages->text("heading", "attach", 
		" (" . $this->messages->text("submit", "Delete") . ")"));
		$files = $this->listFiles($this->vars['resourceId']);
		if(empty($files))
			$this->badInput($this->messages->text("resources", "noAttachments"));
//		foreach($files as $file)
//			$fileSelect[$file] = $file;
		$pString = '';
		if($error)
			$pString .= MISC::p($error, "error", "center");
		$pString .= FORM::formHeader("fileDeleteConfirm");
		$pString .= FORM::hidden("resourceId", $this->vars['resourceId']);
		$pString .= FORM::hidden("userId", $this->session->getVar('setup_userId'));
		$pString .= FORM::selectFBoxValueMultiple(FALSE, "delete", $files, 20);
		$pString .= MISC::br() . $this->messages->text("hint", "multiples") . MISC::br();
		$pString .= MISC::br() . FORM::formSubmit('Delete');
		$pString .= FORM::formEnd();
		$this->template->setVar('body', $pString);
		return $this->template->process();
	}
// Ask for confirmation of delete attachments
	function deleteConfirm()
	{
		if(!array_key_exists('delete', $this->vars) || empty($this->vars['delete']))
			$this->badInput($this->errors->text("inputError", "invalid"));
		$this->template->setVar("heading", $this->messages->text("heading", "attach", 
			" (" . $this->messages->text("submit", "Delete") . ")"));
		$files = $this->listFiles($this->vars['resourceId']);
		$delFiles = "'" . implode("', '", array_keys(array_intersect(array_flip($files), 
			$this->vars['delete']))) . "'";
		$pString = MISC::p($this->messages->text("resources", "deleteConfirmAttach", ": $delFiles"));
		$pString .= FORM::formHeader("fileDelete");
		$pString .= FORM::hidden("resourceId", $this->vars['resourceId']);
		$pString .= FORM::hidden("userId", $this->vars['userId']);
		foreach($this->vars['delete'] as $id)
			$pString .= FORM::hidden("fileDelete_" . $id, $id);
		$pString .= MISC::br() . FORM::formSubmit('Confirm');
		$pString .= FORM::formEnd();
		$this->template->setVar('body', $pString);
		return $this->template->process();
	}
// Deletefiles(s)
	function delete()
	{
		include_once("core/resource/RESOURCEVIEW.php");
		$resource = new RESOURCEVIEW($this->db, $this->vars);
		if($this->session->getVar('attachLock'))
			new CLOSE($this->db, $resource->view($this->errors->text("done", "attachDelete"), 'lastSolo'));
		$input = array();
		foreach($this->vars as $key => $value)
		{
			if(!$value)
				continue;
			if(!preg_match("/fileDelete_/", $key, $matches))
				continue;
			$input[] = $value;
		}
		if(empty($input))
			$this->badInput($this->errors->text("inputError", "invalid"));
		foreach($input as $hash)
		{
			$this->db->delete("WKX_attachments", " WHERE " . 
				$this->db->formatField('hashFilename') . '=' . $this->db->tidyInput($hash) . 
				" AND " . 
				$this->db->formatField('resourceId') . '=' . $this->db->tidyInput($this->vars['resourceId']));
// Is file used by other resources?  If not, unlink it
			$recordSet = $this->db->select(array("WKX_attachments"), array("hashFilename"), 
				" WHERE " . $this->db->formatField("hashFilename") . "=" . $this->db->tidyInput($hash));
			if(!$this->db->numRows($recordSet)) // Unlink it
				unlink($this->dirName . "/" . $hash);
		}
// Lock re-inserting
		$this->session->setVar('attachLock', TRUE);
// send back to view this resource with success message
		new CLOSE($this->db, $resource->view($this->success->text("attach", " " . 
			$this->messages->text("misc", 'deleted') . " "), 'lastSolo'));
	}
/*
* Download a file providing the user filename instead of the hash filename
* @Author Mark Grimshaw
*/
	function download()
	{
		if(!array_key_exists('resourceId', $this->vars) || 
		!array_key_exists('hash', $this->vars) || !array_key_exists('filename', $this->vars))
		{
			include_once("core/resource/RESOURCEVIEW.php");
			$resource = new RESOURCEVIEW($this->db, $this->vars);
			new CLOSE($this->db, $resource->view($this->errors->text("inputError", "missing"), 'lastSolo'));
		}
		$this->incrementDownloadCounter();
		$recordSet = $this->db->select(array("WKX_attachments"), array("fileType", "fileSize"), 
			" WHERE " . $this->db->formatField("hashFilename") . "=" . 
			$this->db->tidyInput($this->vars['hash']));
		$row = $this->db->fetchRow($recordSet);
		$type = $row['fileType'];
		$size = $row['fileSize'];
//rename($this->vars['hash'], $this->vars['fileName']);
		$fileName = $this->vars['filename'];
		$hash = $this->vars['hash'];
		unset($this->vars);
		unset($row);
		unset($this->db);
//		ob_start();
		header("Content-type: $type");
		header( "Content-Disposition: inline; filename=\"$fileName\"; size=\"$size\"" );
		header("Content-Length:  $size" );
		header("Expires:  0");
		header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
//		header("Content-Transfer-Encoding: binary");
		header("Pragma: public");
//		readfile($this->dirName . "/" . $hash);
		$this->readfile_chunked($this->dirName . "/" . $hash);
//		ob_end_flush();
//rename($this->vars['fileName'], $this->vars['hash']);
		die;
	}
// From http://uk3.php.net/function.readfile
	function readfile_chunked($filename)
	{
		$chunksize = 1*(1024*1024); // how many bytes per chunk
		$buffer = '';
		$handle = fopen($filename, 'rb');
		if ($handle === false)
		       return false;
		while (!feof($handle))
		{
	       $buffer = fread($handle, $chunksize);
	       echo $buffer;
	       ob_flush();
	       flush();
		}
		fclose($handle);
	}
// Increment the accesses counter for this resource
	function incrementDownloadCounter()
	{
		$this->db->updateSingle('WKX_resource_misc', 
			$this->db->formatField('attachDownloads') .  "=" . 
			$this->db->formatField('attachDownloads') . "+" . $this->db->tidyInput(1), 
			" WHERE " . $this->db->formatField('id') . '=' . $this->db->tidyInput($this->vars['resourceId']));
	}
// Error handling
	function badInput($error)
	{
		$this->template->setVar("heading", $this->messages->text("heading", "attach"));
		include_once("core/html/CLOSE.php");
		$this->template->setVar('body', $error);
		new CLOSE($this->db, $this->template->process());
	}
}
?>
